#include <base/reflection.hpp>
#include <iostream>
#include <string>
#include <exception>

class Pre {
    public:
        void Print() const {
            std::cout << name_ << "hello world" << std::endl;
        }
    private:
        std::string name_ = "Pre";
};

class Foo {
    public:
        void PassByValue(std::string s) const
        {
            std::cout << "Foo::PassByValue(`" << s << "`)" << std::endl;
        }

        void PassByRef(std::string& s) const 
        {
            std::cout << "Foo::PassByRef(`" << s << "` &)" << std::endl;
        }

        void PassByConstRef(const std::string& s) const 
        {
            std::cout << "Foo::PassByConstRef(const `" << s << "` &)" << std::endl;
        }


        static void MakeReflectable()
        {
            reflect::AddClass<Foo>("Foo")
                .AddMemberVariable("name", &Foo::name)
                .AddMemberVariable("x_", &Foo::x_)
                .AddMemberFunction("PassByValue", &Foo::PassByValue)
                .AddMemberFunction("PassByRef", &Foo::PassByRef)
                .AddMemberFunction("PassByConstRef", &Foo::PassByConstRef)
                .AddMemberFunction("PrintX", &Foo::PrintX)
                .AddMemberFunction("GetX", &Foo::GetX)
                .AddMemberFunction("PrintPre", &Foo::PrintPre)
                .AddMemberFunction("PrintTwoArgs", &Foo::PrintTwoArgs);
        }

        void PrintX() const
        {
            std::cout <<  x_ << std::endl;
        }

        int GetX() const
        {
            return x_;
        }
        
        void PrintPre() const
        {
            pre_.Print();
        }

        void PrintTwoArgs(int a, double b) const 
        {
            std::cout << a << " " << b << std::endl;
        }
        std::string name;
    private:
        int x_{0};
        Pre pre_;
};


void TestFoo()
{
    std::cout << ">>> TestMemberFunction Start" << std::endl;
    Foo foo;
    Foo::MakeReflectable();
    using namespace reflect;
    auto str = reflect::GetByName("Foo").GetMemberVariable("name");
    str.SetValue(foo, std::string("Hello World"));
    std::cout << str.GetValue<std::string>(foo) << std::endl;
    std::cout << foo.GetX() << std::endl;
    auto x = reflect::GetByName("Foo").GetMemberVariable("x_");
    std::cout << x.GetValue<int>(foo) << std::endl;
    x.SetValue(foo, 50);
    std::cout << x.GetValue<int>(foo) << std::endl;
    std::cout << foo.GetX() << std::endl;

    //reflect::GetByName("Foo").GetMemberFunction("PassByValue").Invoke(foo, std::string("Hello I am PassByValue"));
    std::cout << reflect::GetByName("Foo").GetMemberFunction("PassByValue").IsConst() << std::endl;
    std::string str2 = "Hello I am PassByRef";
    reflect::GetByName("Foo").GetMemberFunction("PassByRef").Invoke(foo, str2);
    std::string str3 = "Hello I am PassByConstRef";
    reflect::GetByName("Foo").GetMemberFunction("PassByConstRef").Invoke(foo, str3);
    // reflect::GetByName("Foo").GetMemberFunction("PassByConstRef").Invoke(foo, std::cref(str3));
    reflect::GetByName("Foo").GetMemberFunction("PrintX").Invoke(foo);
    std::cout << std::any_cast<int>(GetByName("Foo").GetMemberFunction("GetX").Invoke(foo)) << std::endl;
    std::cout << foo.GetX() << std::endl;

    Foo Foo_obj1;
    x = reflect::GetByName("Foo").GetMemberVariable("x_");
    std::cout << x.GetValue<int>(Foo_obj1) << std::endl;
    x.SetValue(Foo_obj1, 100);
    std::cout << x.GetValue<int>(Foo_obj1) << std::endl;
    reflect::GetByName("Foo").GetMemberFunction("PrintPre").Invoke(Foo_obj1);
    std::cout << ">>> TestMemberFunction Finish" << std::endl;
}

void TestCatchFoo() 
{
    std::cout << ">>> " << "TestCatchFoo Start" << std::endl;
    Foo* obj = new Foo();
    try {
        reflect::GetByName("Foo").GetMemberFunction("PrintTwoArgs").Invoke(*obj);
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
    std::cout << ">>> " << "TestCatchFoo End" << std::endl;
}

int main()
{
    TestFoo();
    TestCatchFoo();
}